/**
 *  @file          flags-groups.c
 *  @date          Time-stamp: <2013-12-10 20:24:49, by lyzh>
 *  @brief         
 *  @version       1.0.0
 *  @author        Liangyaozhan
 *                 ivws02@gmail.com
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include "flags-groups.h"

#ifndef PATH_MAX
#include <linux/limits.h>
#endif

#ifndef TEST_EN
#define TEST_EN 0
#endif

#if TEST_EN==0
#define fprintf(...) do{}while( 0)
#endif


char *find_next_group( const char *p, const char **next );
void group_write_dep_name( struct group *_this );
void group_merge( struct group *_this );
struct group *group_list_match( struct group_list *_this, const char *haystack );

#define ITER_WHEN(p, isfunc, op)        do{ while (p && isfunc(*p))p op;}while (0)
#define TOKEN_STRIP(pleft, pright) do{                              \
        ITER_WHEN( pleft, ( pleft<pright )&&!isgraph, ++);          \
        ITER_WHEN( pright, ( pleft<pright )&&!isgraph, --);         \
    }while ( 0 )



void group_show( struct group *_this )
{
    int i;
    struct group_dep *gd;

    fprintf( stderr,
             "group_name:     :%s\n"
             "group_argc:     :%d\n"
             "group_match_path:%s\n"
             "group_dependence:",
             _this->name, _this->argc, _this->path );
    list_for_each_entry( gd, &_this->dependence_head, node )
    {
        if ( gd->grp )
        {
            assert( 0 == strcmp(gd->name, gd->grp->name) );
            fprintf( stderr, " %s ", gd->name );
        } else {
            fprintf( stderr, " %s(NULL)", gd->name );
        }
    }
    fprintf( stderr, "\norigin args:{\n" );
    
    for ( i=0; i<_this->argc; i++ )
    {
        fprintf( stderr, "%d:%s\n", i, _this->argv[i] );
    }
    fprintf( stderr, "}\n" );

    fprintf( stderr, "Merged args:{\n" );
    
    for ( i=0; i<_this->merged_argc; i++ )
    {
        fprintf( stderr, "%d:%s\n", i, _this->merged_argv[i] );
    }
    fprintf( stderr, "}\n$" );
    fflush( stderr );
}

#if TEST_EN>0
    
int main (int argc, char * argv[]) 
{
#if 0
    const char *src = ""
        "gn1|gn2 gn3:/path/1{-Iitem11 -Iitem12}"
        "gn2|gn1 gn3:/path/2{-Iitem21 -Iitem22}"
        "gn3| gn1 gn2  :/path/3{-Iitem31 -Iitem32}";

    struct group_list gl;

    group_list_init( &gl );

    group_list_get( &gl, src );
    group_list_resolve( &gl );
    group_list_show( &gl );

    group_list_destroy( &gl );

    printf( "bellow from file test.txt\n" );
#endif
    
    struct group_list gl;
    const char *file;
    group_list_init( &gl );
    file = find_file();
    if ( !file )
    {
        printf( "cannot find file\n" );
        return -1;
    }
    group_list_get_from_file( &gl, file );
    group_list_resolve( &gl );
    group_list_merge( &gl );
    group_list_show( &gl );

    if ( argc > 1 )
    {
        struct group *grp_max = group_list_match( &gl, argv[ 1 ] );
        if ( grp_max )
        {
            printf( "max match group %s found:", argv[ 1 ] );
            group_show( grp_max );
        } else {
            printf( "cannot find max group %s\n", argv[ 1 ] );
        }
    }
    
    group_list_destroy( &gl );

    printf( "\n" );
    return 0;
}
#endif

int group_dependence( const struct group *_this, const struct group *subgroup )
{
    struct group *grp;

    list_for_each_entry(grp, &_this->dependence_head, node)
    {
        if ( 0 == strcmp( grp->name, subgroup->name ) )
        {
            return 1;
        }
    }
    return 0;
}

int group_list_check_dependence( const struct group_list *_this )
{
    struct group *grp;
    
    list_for_each_entry( grp, &_this->head, node )
    {
        
    }
    return 1;//OK
}

void group_dep_resolve( struct group_dep *_this, const struct list_head *head )
{
    struct group *grp;

    list_for_each_entry(grp, head, node)
    {
        if ( 0 == strcmp( grp->name, _this->name ) )
        {
            _this->grp = grp;
        }
    }
}

void group_write_dep_name( struct group *_this )
{
    char *seperator = strchr( _this->name, '|' );
    char *p_end = _this->name + strlen( _this->name );
    if ( seperator )
    {
        char *p = seperator;
        p--;
        ITER_WHEN(p, !isgraph, --);
        *++p = 0;
        char *left;
        char *right;
        
        left = seperator + 1;
        while ( *left && ( left < p_end ) )
        {
            ITER_WHEN(left, ( left < p_end )&&*left&&!isgraph, ++);
            right = left;
            ITER_WHEN(right, ( right < p_end )&&isgraph, ++);
            if ( ( right == left ) && ( right==p_end ) )
            {
                break;
            }
            struct group_dep *gd = malloc(sizeof( struct group_dep ));
            assert(gd);
            group_dep_init( gd );
            gd->name = strndup( left, right-left );
            list_add_tail(&gd->node, &_this->dependence_head );
            left = right;
        }
    }
}

void group_dep_init( struct group_dep *_this )
{
    memset( _this, 0x00, sizeof( *_this ) );
}

void group_dep_destroy( struct group_dep *_this )
{
    ( void )_this;
}

void group_init( struct group *_this )
{
    memset( _this, 0x00, sizeof( *_this ) );
    INIT_LIST_HEAD( &_this->dependence_head );
}

void group_resolve( struct group *_this, const struct list_head *head )
{
    struct group_dep *gd;

    list_for_each_entry(gd, &_this->dependence_head, node)
    {
        group_dep_resolve(gd, head);
    }
}

void group_list_resolve( struct group_list *_this )
{
    struct group *grp;
    list_for_each_entry( grp, &_this->head, node )
    {
        group_resolve( grp, &_this->head);
    }
}

void group_list_show( struct group_list *_this )
{
    struct group *grp;
    list_for_each_entry( grp, &_this->head, node )
    {
        group_show( grp );
    }
}

void group_depart( struct group *_this, const char *str )
{
    const char *p;
    const char *p_tmp;
    const char *p_end;
    const char *start;
    const char *seperator;
    int  n;

    _this->argc = 0;
    _this->argv = NULL;
    
    ITER_WHEN(str, !isgraph, ++);
    p = str;
    //ITER_WHEN(p, isgraph, ++);
    seperator = strchr(str,':');
    if ( !seperator )
    {
        fprintf( stderr, "ERROR: missing ':'\n" );
        _this->name = strdup("");
        _this->path = strdup("");
        return;
    }
    
    ITER_WHEN(p, '{' !=, ++);
    p_tmp = seperator-1;
    ITER_WHEN(p_tmp, !isgraph, --);
    assert(p_tmp>=str);
    _this->name = strndup( str, p_tmp-str+1 );// name & dependence
    group_write_dep_name( _this );
    seperator++;
    //ITER_WHEN(seperator, !isgraph, ++);
    p_tmp = p-1;
    //ITER_WHEN(p_tmp, !isgraph, --);
    TOKEN_STRIP( seperator, p_tmp );
    p_tmp++;
    if ( p_tmp>seperator )
    {
        _this->path = strndup( seperator, p_tmp-seperator );
    } else {
        _this->path = strdup( "" );
    }
    p++;
    ITER_WHEN(p, *p && !isgraph, ++ );
    start = p;
    n = 0;
    while ( *p && *p !='}' )
    {
        p_end = p;
        ITER_WHEN(p_end, isgraph, ++ );
        // new flag
        n++;
        p = p_end;
        ITER_WHEN(p, *p && !isgraph, ++ );
        if ( *p == '}' )
        {
            break;
        }
    }
    _this->argc = n;
    _this->argv = calloc(sizeof(char*), n);
    int i;
    p = start;
    for (i=0; i<n; i++)
    {
        p_end = p;
        ITER_WHEN(p_end, isgraph, ++ );
        _this->argv[i] = strndup(p, p_end-p);
        p = p_end;
        ITER_WHEN(p, *p && !isgraph, ++ );
    }

}

void group_destroy( struct group *_this )
{
    int i;
    struct group_dep *gd;
    struct group_dep *n;

    list_for_each_entry_safe( gd, n, &_this->dependence_head, node )
    {
        group_dep_destroy( gd );
        free( gd );
    }
    
    for ( i=0; i<_this->argc; i++ )
    {
        free( _this->argv[i] );
    }

    for ( i=0; i<_this->merged_argc; i++ )
    {
        free( _this->merged_argv[ i ] );
    }
}

char *find_next_group( const char *p, const char **next )
{
    const char *p_left;
    const char *p_right;

    ITER_WHEN(p, *p && !isgraph, ++ );
    p_left = strchr( p, '{' );
    if ( !p_left )
    {
        return NULL;
    }
    
    p_right = strchr( p_left, '}' );
    if ( !p_right )
    {
        return NULL;
    }
    
    *next = p_right+1;
    return strndup( p, p_right+1-p );
}

void group_list_get_from_file( struct group_list *_this, const char *filename )
{
    char *file_buffer = NULL;
    FILE *fp;
    fp = fopen( filename, "r" );
    if ( !fp )
    {
        fprintf( stderr, "cannot open file: %s\n", filename );
        return ;
    }
    
    int file_size;
    fseek(fp, 0, SEEK_END );
    file_size = ftell(fp);
    fseek(fp, 0, SEEK_SET );
    file_buffer = malloc(file_size+1);
    assert(file_buffer);
    int read_size = fread( file_buffer, 1, file_size, fp );
    file_buffer[read_size] = 0;

    char *buffer;
    // remove comment
    buffer = malloc( file_size );
    assert( buffer );
    char *p_comment;
    char *p_start = file_buffer;
    char *to = buffer;
    p_comment = strchr( file_buffer, '#' );
    
    while ( p_comment && p_start && *p_start )
    {
        if ( p_comment!=p_start )
        {
            strncpy( to, p_start, p_comment-p_start );
            to += p_comment-p_start;
        }
        p_start = strchr( p_comment, '\n' );
        if ( p_start )
        {
            p_start++;
        }
        p_comment = strchr( p_start, '#' );
    }
    if ( !p_comment && p_start )
    {
        strcpy( to, p_start );
    }
    
    group_list_get( _this, buffer );
    
    free( file_buffer );
    free( buffer );
}

void group_list_get( struct group_list *_this, const char *buffer )
{
    char *p;
    const char *next = NULL;
    p = find_next_group( buffer, &next );
    while ( p )
    {
        struct group *grp;
        grp = (struct group*)malloc( sizeof(struct group) );
        assert( grp );
        group_init( grp );
        group_depart( grp, p );
        list_add_tail( &grp->node, &_this->head );
        free(p);
        p = find_next_group( next, &next );
    }
}


void group_list_init( struct group_list *_this )
{
    memset( _this, 0x00, sizeof( *_this ) );
    INIT_LIST_HEAD( &_this->head );
}

void group_list_destroy( struct group_list *_this )
{
    struct group *grp;
    struct group *n;

    list_for_each_entry_safe( grp, n, &_this->head, node )
    {
        group_destroy( grp );
        free( grp );
    }
}

void group_set_merge_count( struct group *_this )
{
    struct group_dep *gd;
    
    _this->merged_argc = _this->argc;;
    list_for_each_entry(gd, &_this->dependence_head, node)
    {
        if ( gd->grp )
        {
            // deep first
            group_merge( gd->grp );
            _this->merged_argc += gd->grp->merged_argc;
        }
    }
}
    
void group_merge( struct group *_this )
{
    int count;

    if ( _this->merged_argc )
    {
        return;
    }

    // step 1: set merge count
    group_set_merge_count( _this );
    count = _this->merged_argc;
    
    // step 2: allocate memory
    _this->merged_argv = calloc(sizeof(char*), count);
    assert( _this->merged_argv );
    char **argv = _this->merged_argv;

    // step 3: copy this group's args
    int i;
    for ( i=0; i<_this->argc; i++ )
    {
        *argv++ = strdup(_this->argv[i]);
    }

    // step 4: copy dependency args
    struct group_dep *gd;
    list_for_each_entry(gd, &_this->dependence_head, node)
    {
        if ( gd->grp )
        {
            int n = gd->grp->merged_argc;
            for ( i=0; i<n; i++ )
            {
                *argv++ = strdup( gd->grp->merged_argv[ i ] );
            }
        }
    }
}

void group_list_merge( struct group_list *_this )
{
    struct group *grp;

    list_for_each_entry( grp, &_this->head, node )
    {
        group_merge( grp );
    }
}


int group_match( struct group *_this, const char *haystack )
{
    char *p = strstr( haystack, _this->path );
    if ( p )
    {
        return strlen( _this->path );
    }
    return 0;
}

/**
 *  @brief match
 *
 *  return group.
 */
struct group *group_list_match( struct group_list *_this, const char *haystack )
{
    struct group *grp;
    struct group *grp_max = NULL;
    int len_max = 0;//最大匹配长度

    list_for_each_entry( grp, &_this->head, node )
    {
        int len = group_match( grp, haystack);
        if ( len > len_max )
        {
            len_max = len;
            grp_max = grp;
        }
    }
    return grp_max;
}


const char *find_file( void )
{
    char *file=NULL;
    static char buffer[ PATH_MAX ];

    char *pwd;
    fprintf( stdout, "try to use environment variable $PWD " );
    pwd = getenv( "PWD" );
    if ( pwd )
    {
        FILE *fp;
        char pwd_cp[ PATH_MAX ];

        strncpy( pwd_cp, pwd, sizeof( buffer ) );

        /*
         *  当前目录下的文件优先
         *  往上级目录找文件
         */
        while ( !file && strlen(pwd_cp) )
        {
            strncpy( buffer, pwd_cp, sizeof( buffer ) );
            strncat( buffer, "/flags.txt", strlen( "/flags.txt" ));
            fprintf( stdout, "try to find file %s ", buffer );
            fp = fopen( buffer, "r" );
            if ( fp )
            {
                fclose( fp );
                file = buffer;
                fprintf( stdout, "-->OK\n" );
                break;
            } else {
                fprintf( stdout, "-->FAILED\n" );
            }
            char *p;
            p = strrchr( pwd_cp, '/' );
            if ( p )
            {
                *p = 0;
            }
        }
    } else {
        fprintf( stdout, "--> NOT SPECIFIED\n" );
    }

    /*
     *  当前目录和父级目录都没找到文件
     *  就使用环境变量指定的文件
     */
    if ( !file ) {
        fprintf( stdout, "try to use environment variable AC_FLAGS_FILE " );
        file = getenv( "AC_FLAGS_FILE" );
        if ( !file )
        {
            char *home;
            buffer[ 0 ] = 0;
            fprintf( stdout, "--> NOT SPECIFIED\n" );
            fprintf( stdout, "try to find ${HOME}/flags.txt " );
            home = getenv( "HOME" );
            if ( home )
            {
                fprintf( stdout, "-->OK\n" );
                strncpy( buffer, home, sizeof( buffer ) );
            } else {
                fprintf( stdout, "-->FAILED\n" );
            }
            strncat( buffer, "/flags.txt", strlen( "/flags.txt" ));
            file = buffer;    
        } else {
            fprintf( stdout, "-->AC_FLAGS_FILE=%s\n", file );
        }
    }
#if TEST_EN>0
    fflush( stdout );
#endif
    return file;
}
